oo::class create DebugMenu {
    superclass AppMenu
    variable Menu Label NotebookObj Notebook BreakPoint \
        DebugArgv ScriptHead Thread InfoVars \
        PopupArgvDialog PopupInfoVarsDialog PopupAlertDialog \
        ThreadError ThreadResult ThreadStdout Over

    constructor {menubar} {
        next $menubar debug
        set DebugArgv {}
        set ScriptHead {
            namespace eval __Debug__ {
                variable Result {}
                variable Error {}
                variable InfoVars {}
                variable Stdout {}
                variable DebugFlag 0
                proc flapFlag {} {
                    variable DebugFlag
                    if $DebugFlag {
                        set DebugFlag 0
                    } else {
                        set DebugFlag 1
                    }
                }
                proc infoVars {} {
                    variable InfoVars
                    set ret {}
                    set level [uplevel info level]
                    # _puts $level
                    set l $level
                    while {$l >= 0} {
                        set gap  [expr $level-$l+1]
                        set vars {}
                        foreach v [uplevel $gap info vars] {
                            if [uplevel $gap array exists $v] {
                                set val [uplevel $gap array get $v]
                            } else {
                                set val [uplevel $gap set $v]
                            }
                            lappend vars [list $v $val]
                            # [uplevel $gap set $v]
                        }
                        lappend ret [list $l $vars]
                        incr l -1
                    }
                    set InfoVars $ret
                }
            }
            rename exit _exit
            proc exit {} { return -code return }
            rename puts _puts
            proc puts {args} {
                set len [llength $args]
                if {$len == 1} {
                    append ::__Debug__::Stdout [lindex $args end]\n
                } elseif {$len == 2 && [lindex $args 0] == "-nonewline"} {
                    append ::__Debug__::Stdout [lindex $args end]
                } else {
                    uplevel _puts $args
                }
            }
            catch {
                wm protocol . WM_DELETE_WINDOW {destroy .; exit}
            }
            thread::wait
        }
        set Thread {}
        set Over {}
        trace add variable [info object namespace [self object]]::Over write [list [self object] threadStopped]
        set PopupArgvDialog [PopupArgvDialog new .argv]
        set PopupInfoVarsDialog [PopupInfoVarsDialog new .info]
        set PopupAlertDialog [PopupAlertDialog new .done]

        set InfoVars {}
        set ThreadStdout {}
        set ThreadResult {}
        set ThreadError {}
    }

    method createMenu {} {
        foreach row {
            {command {{Flap breakpoint} F4 0 flapBreakPoint}}
            {command {{Clear all} {} {} clearAllBreakPoints}}
            {separator {}}
            {command {Run F5 0 runScript }}
            {command {Debug A-F5 0 debugScript}}
            {command {{Set argv} {} {} setArgv}}
            {separator {}}
            {command {Continue F6 {} continueDebug}}
            {command {{Display info} F7 {} displayInfo}}
            {separator {}}
            {command {Stop {} {} stopDebug}}
        } {
            my createItem $row
        }
        my disableControlUI
    }

    method getBreakPointObj {} {
        set padWidget [my getCurrentPadWidget]
        foreach o [info class instances ::BreakPoint] {
            if {"_$padWidget" == [set [info object namespace $o]::RenamedPadWidget]} {
                break
            }
        }
        if [info exists o] {
            return $o
        }
    }

    method flapBreakPoint {} {
        set padWidget [my getCurrentPadWidget]
        set obj [my getBreakPointObj]
        if {$obj != {}} {
            $obj flapBreakPointAtIndex [$padWidget index insert]
        }
    }

    method clearAllBreakPoints {} {
        set padWidget [my getCurrentPadWidget]
        set bkObj [my getBreakPointObj]
        foreach {id _} [$padWidget tag ranges breakpoint] {
            $bkObj unsetBreakPoint $id
        }
    }

    method enableControlUI {} {
        foreach id {0 1 3 4 5} {
            $Menu entryconfigure $id -state disable
        }
        foreach id {7 10} {
            $Menu entryconfigure $id -state normal
        }
    }

    method disableControlUI {} {
        foreach id {0 1 3 4 5} {
            $Menu entryconfigure $id -state normal
        }
        foreach id {7 10} {
            $Menu entryconfigure $id -state disable
        }
    }

    method createDebugScript {script breakpoints} {
        set ret {}
        append ret [list set argv $DebugArgv]
        append ret "\nif \[catch \{\n"
        set i 1;
        foreach l [split $script \n\r] {
            foreach b $breakpoints {
                if {$i == int($b)} {
                    append ret {
                        ::__Debug__::infoVars
                        vwait ::__Debug__::DebugFlag
                    }
                    break
                }
            }
            append ret $l\n
            incr i
        }
        append ret "\} res\] {
                set ::__Debug__::Error \$::errorInfo
                set ::__Debug__::Result {}
            } else {
                set ::__Debug__::Result \$res
                set ::__Debug__::Error {}
            }"
        return $ret
    }

    method createRunScript {script} {
        set ret {}
        append ret [list set argv $DebugArgv]
        append ret "\nif \[catch \{\n"
        append ret $script
        append ret "\} res\] {
                set ::__Debug__::Error \$::errorInfo
                set ::__Debug__::Result {}
            } else {
                set ::__Debug__::Result \$res
                set ::__Debug__::Error {}
            }"
        return $ret
    }

    method resetThread {} {
        variable Thread;        # When redefined with objdefine, the variabel need to be re-declared.
        set Thread {}
        my disableControlUI
    }

    method stopDebug {{ifPopup 1}} {
        if {$Thread != {} && [thread::exists $Thread]} {
            my setThreadInfo
            thread::release $Thread
            my resetThread
            if $ifPopup {
                if {$ThreadError != {}} {
                    $PopupAlertDialog popup "Error!"
                } else {
                    $PopupAlertDialog popup "Done!"
                }
            }
        }
    }

    method createDebugThread {} {
        my stopDebug 0
        my resetThreadInfo
        set Thread [thread::create $ScriptHead]
        thread::configure $Thread -unwindonerror 1
    }

    method xScript {script} {
        variable Thread;        # for objdefine
        my createDebugThread
        my enableControlUI
        thread::send -async $Thread $script \
            [info object namespace [self object]]::Over
    }

    method runScript {} {
        set padWidget [my getCurrentPadWidget]
        set content [$padWidget get 1.0 end-1c]
        my xScript [my createRunScript $content]
    }

    method debugScript {} {
        set padWidget [my getCurrentPadWidget]
        set content [$padWidget get 1.0 end-1c]
        set breakpoints {}
        foreach {b _} [$padWidget tag ranges breakpoint] {
            lappend breakpoints $b
        }
        my xScript [my createDebugScript $content $breakpoints]
        set BreakPoint [lindex $breakpoints 0]
        if {$BreakPoint != {}} {
            $padWidget mark set insert $BreakPoint
        }
    }

    method setThreadInfo {} {
        if {$Thread != {}} {
            foreach v {Vars Stdout Result Error} {
                my info$v
            }
        }
    }

    method threadStopped {args} {
        my stopDebug
    }

    method continueDebug {} {
        if {$Thread == {}} { return }
        thread::send $Thread ::__Debug__::flapFlag
        if {$BreakPoint != {}} {
            set padWidget [my getCurrentPadWidget]
            set BreakPoint [lindex [$padWidget tag nextrange breakpoint $BreakPoint+1c] 0]
            if {$BreakPoint != {}} {
                $padWidget mark set insert $BreakPoint
            }
        }
    }

    method resetThreadInfo {} {
        foreach v {ThreadStdout ThreadResult ThreadError} {
            set $v {}
        }
    }

    method displayInfo {} {
        my setThreadInfo
        $PopupInfoVarsDialog popup "Thread Info" $InfoVars $ThreadError $ThreadResult $ThreadStdout
    }

    method infoResult {} {
        set ThreadResult [thread::send $Thread {set ::__Debug__::Result}]
    }

    method infoStdout {} {
        set ThreadStdout [thread::send $Thread {set ::__Debug__::Stdout}]
    }

    method infoVars {} {
        set InfoVars [thread::send $Thread {set ::__Debug__::InfoVars}]
    }

    method infoError {} {
        set err [thread::send $Thread {set ::__Debug__::Error}]
        regsub -all {\s*::__Debug__::infoVars\s*\n?} $err {} err
        regsub -all {\s*vwait ::__Debug__::DebugFlag\s*\n?} $err {} err
        set ThreadError $err
    }

    method setArgv {} {
        set DebugArgv [$PopupArgvDialog popup "Cmd line argv"]
    }
}